#include <iostream>
#include <vector>
using namespace std;

int n; // 共计N个数
int m; // 选m个数
// 定义三个vector容器，分别存储
vector<string> name; // 数的名称name
vector<string> ans;  // 最终结果ans
vector<int> chosen;  // 已选数的索引chosen

void calc(int x) // 递归函数,x是当前处理的数的索引
{

    // 如果已选择数字的数量（chosen.size()）大于给定的目标数量m，或者剩余未处理的数字与已选数字之和小于m，
    // 则说明当前路径不可能找到符合条件的组合，直接返回以结束此次递归调用。
    if (chosen.size() > m || chosen.size() + (n - x + 1) < m) // 剪枝
        return;

    // 当x等于n+1时，表示已经遍历完所有可能的选择。此时若已选择的数字数量正好为m，则进行以下操作：
  
    if (x == n + 1)
    {
        // 创建一个空字符串ansTem，然后遍历已选择的数字集合chosen，
        // 将每个对应位置的name数组元素（减去1后作为索引）添加到ansTem中，并在每个元素间添加空格
        // vector容器的元素索引从0开始，所以需要减去1,以便与name数组的索引对应。
        // 最后将ansTem存入ans中。
        // 例如，假设已选择的数字集合chosen为{1,3,5}，则ansTem为"A B C"。
        // 注意，ansTem末尾没有空格，因为最后一个元素后面没有元素了。
        // 另外，ansTem的大小为m，因为我们只选择了m个数。
        // 最后，ansTem存入ans中，并返回。
        string ansTem = ""; //
        for (int i = 0; i < chosen.size(); i++)
            ansTem += name[chosen[i] - 1] + " ";
        ans.push_back(ansTem);
        return;
    }
    
	calc(x + 1);// 先调用，表示不选择当前数字x，继续向下递归
    chosen.push_back(x);//将当前数字x加入chosen中
    

    // 将刚刚加入的数字x从chosen中移除（回溯），再次调用calc(x+1)以尝试不选择当前数字x的情况
    // 【消除痕迹】在递归算法中，回溯是一种策略，用于遍历问题的所有可能解空间。在这个函数中，
    // 当我们通过chosen.push_back(x)将当前处理的数x加入到已选择的集合chosen中，并继续进行下一次递归调用时，
    // 实际上是在探索一种可能性：即选择了索引为x的数作为解的一部分。然而，在递归树的深度优先搜索过程中，
    // 我们需要尝试所有可能的选择路径，包括不选择当前索引x的情况。因此，在对x+1进行第二次递归调用之前，
    // 我们先执行了chosen.pop_back()操作，这一步骤就是所谓的“回溯”。它将刚刚添加进来的元素x从已选择的集合中移除，
    // 使得接下来的递归调用能够基于一个不包含x的选择集合作决策。通过这样的回溯机制，该函数能够全面地生成
    // 所有符合条件（即选择了m个数）的组合，而不会遗漏任何可能的解。
    calc(x + 1);//继续向下递归
	chosen.pop_back(); //将chosen中的最后一个数字弹出，以便尝试其他组合
    
}
int main()
{

    cin >> n >> m;
    for (int i = 0; i < n; i++)
    {
        string s;
        cin >> s;
        name.push_back(s); // 将n个数的名称存入name中
    }
    calc(1); // 计算所有可能的组合
    for (int i = ans.size() - 1; i >= 0; i--)
        cout << ans[i] << endl; // 将结果存入ans中并输出
}